/*
 * Copyright 2011 OpenWAF.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
/*
 * Original source from Google Web Toolkit(GWT) 
 * http://code.google.com/webtoolkit/
 *
 * Modified to adapt OpenWAF Framework 
 */

/* Original GWT License */

/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package java.lang;

import java.io.Serializable;

public final class Character implements Comparable<Character>, Serializable {

    static class CharSequenceAdapter implements CharSequence {

        private char[] charArray;
        private int start;
        private int end;

        public CharSequenceAdapter(char[] charArray) {
            this(charArray, 0, charArray.length);
        }

        public CharSequenceAdapter(char[] charArray, int start, int end) {
            this.charArray = charArray;
            this.start = start;
            this.end = end;
        }

        public char charAt(int index) {
            return charArray[index + start];
        }

        public int length() {
            return end - start;
        }

        public java.lang.CharSequence subSequence(int start, int end) {
            return new CharSequenceAdapter(charArray, this.start + start,
                    this.start + end);
        }
    }

    private static class CharChache {
        private static Character[] values = new Character[128];
    }
    public static final int MIN_RADIX = 2;
    public static final int MAX_RADIX = 36;
    public static final char MIN_VALUE = '\u0000';
    public static final char MAX_VALUE = '\uFFFF';
    public static final char MIN_SURROGATE = '\uD800';
    public static final char MAX_SURROGATE = '\uDFFF';
    public static final char MIN_LOW_SURROGATE = '\uDC00';
    public static final char MAX_LOW_SURROGATE = '\uDFFF';
    public static final char MIN_HIGH_SURROGATE = '\uD800';
    public static final char MAX_HIGH_SURROGATE = '\uDBFF';
    public static final int MIN_SUPPLEMENTARY_CODE_POINT = 0x10000;
    public static final int MIN_CODE_POINT = 0x0000;
    public static final int MAX_CODE_POINT = 0x10FFFF;
    public static final int SIZE = 16;
    private final transient char value;

    public Character(char value) {
        this.value = value;
    }

    public static int charCount(int codePoint) {
        return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1;
    }

    public char charValue() {
        return value;
    }

    public static int codePointAt(char[] a, int index) {
        return codePointAt(new CharSequenceAdapter(a), index, a.length);
    }

    public static int codePointAt(char[] a, int index, int limit) {
        return codePointAt(new CharSequenceAdapter(a), index, limit);
    }

    public static int codePointAt(CharSequence seq, int index) {
        return codePointAt(seq, index, seq.length());
    }

    public static int codePointBefore(char[] a, int index) {
        return codePointBefore(new CharSequenceAdapter(a), index, 0);
    }

    public static int codePointBefore(char[] a, int index, int start) {
        return codePointBefore(new CharSequenceAdapter(a), index, start);
    }

    public static int codePointBefore(CharSequence cs, int index) {
        return codePointBefore(cs, index, 0);
    }

    public static int codePointCount(char[] a, int offset, int count) {
        return codePointCount(new CharSequenceAdapter(a), offset, offset + count);
    }

    public static int codePointCount(CharSequence seq, int beginIndex, int endIndex) {
        int count = 0;
        for (int index = beginIndex; index < endIndex;) {
            char ch = seq.charAt(index++);
            if (isHighSurrogate(ch) && index < endIndex && (isLowSurrogate(seq.charAt(index)))) {
                ++index;
            }
            ++count;
        }
        return count;
    }

    public int compareTo(Character anotherCharacter) {
        return value - anotherCharacter.value;
    }

    public static int digit(char ch, int radix) {
        if (radix < MIN_RADIX || radix > MAX_RADIX) {
            return -1;
        }

        if (ch >= '0' && ch < '0' + Math.min(radix, 10)) {
            return ch - '0';
        }

        if (ch >= 'a' && ch < (radix + 'a' - 10)) {
            return ch - 'a' + 10;
        }

        if (ch >= 'A' && ch < (radix + 'A' - 10)) {
            return ch - 'A' + 10;
        }
        return -1;
    }

    public static int digit(int codePoint, int radix) {
        return digit((char) codePoint, radix);
    }

    @Override
    public boolean equals(Object obj) {
        return (obj instanceof Character) && (((Character) obj).value == value);
    }

    public static char forDigit(int digit, int radix) {
        if (digit < 0 || digit >= radix) {
            return 0;
        }
        if (radix < MIN_RADIX || radix > MAX_RADIX) {
            return 0;
        }
        if (digit < 10) {
            return (char) ('0' + digit);
        } else {
            return (char) ('a' + digit - 10);
        }
    }

    public static int getNumericValue(char ch) {
        return (int) ch;
    }

    public static int getNumericValue(int codePoint) {
        return codePoint;
    }

    @Override
    public int hashCode() {
        return hashCode(value);
    }

    public static int hashCode(char c) {
        return c;
    }

    public static native boolean isDigit(char ch) /*-{
    return (null != String.fromCharCode(ch).match(/\d/));
    }-*/;

    public static boolean isDigit(int c) {
        return isDigit((char) c);
    }

    public static boolean isHighSurrogate(char ch) {
        return ch >= MIN_HIGH_SURROGATE && ch <= MAX_HIGH_SURROGATE;
    }

    public static native boolean isLetter(char c) /*-{
    return (null != String.fromCharCode(c).match(/[A-Z]/i));
    }-*/;

    public static native boolean isLetter(int codePoint) /*-{
    return (null != String.fromCharCode(codePoint).match(/[A-Z]/i));
    }-*/;

    public static native boolean isLetterOrDigit(char ch) /*-{
    return (null != String.fromCharCode(ch).match(/[A-Z\d]/i));
    }-*/;

    public static boolean isLowerCase(char ch) {
        return isLetter(ch) && ch == toLowerCase(ch);
    }

    public static boolean isLowSurrogate(char ch) {
        return ch >= MIN_LOW_SURROGATE && ch <= MAX_LOW_SURROGATE;
    }

    @Deprecated
    public static boolean isSpace(char c) {
        switch (c) {
            case ' ':
                return true;
            case '\n':
                return true;
            case '\t':
                return true;
            case '\f':
                return true;
            case '\r':
                return true;
            default:
                return false;
        }
    }

    public static boolean isSupplementaryCodePoint(int codePoint) {
        return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT && codePoint <= MAX_CODE_POINT;
    }

    public static boolean isSurrogatePair(char highSurrogate, char lowSurrogate) {
        return isHighSurrogate(highSurrogate) && isLowSurrogate(lowSurrogate);
    }

    public static boolean isUpperCase(char ch) {
        return isLetter(ch) && toUpperCase(ch) == ch;
    }

    public static boolean isValidCodePoint(int codePoint) {
        return codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT;
    }

    public static native boolean isWhitespace(char ch)/*-{
    return ' \t\n\r\v'.indexOf(String.fromCharCode(ch)) > -1 ;
    }-*/;

    public static int offsetByCodePoints(char[] a, int start, int count, int index, int codePointOffset) {
        return offsetByCodePoints(new CharSequenceAdapter(a, start, count), index,
                codePointOffset);
    }

    public static int offsetByCodePoints(CharSequence seq, int index, int codePointOffset) {
        if (codePointOffset < 0) {

            while (codePointOffset < 0) {
                --index;
                if (Character.isLowSurrogate(seq.charAt(index))
                        && Character.isHighSurrogate(seq.charAt(index - 1))) {
                    --index;
                }
                ++codePointOffset;
            }
        } else {
            while (codePointOffset > 0) {
                if (Character.isHighSurrogate(seq.charAt(index))
                        && Character.isLowSurrogate(seq.charAt(index + 1))) {
                    ++index;
                }
                ++index;
                --codePointOffset;
            }
        }
        return index;
    }

    public static char[] toChars(int codePoint) {
        if (codePoint < 0 || codePoint > MAX_CODE_POINT) {
            throw new IllegalArgumentException();
        }
        if (codePoint >= MIN_SUPPLEMENTARY_CODE_POINT) {
            return new char[]{
                        getHighSurrogate(codePoint),
                        getLowSurrogate(codePoint),};
        } else {
            return new char[]{
                        (char) codePoint,};
        }
    }

    public static int toChars(int codePoint, char[] dst, int dstIndex) {
        if (codePoint < 0 || codePoint > MAX_CODE_POINT) {
            throw new IllegalArgumentException();
        }
        if (codePoint >= MIN_SUPPLEMENTARY_CODE_POINT) {
            dst[dstIndex++] = getHighSurrogate(codePoint);
            dst[dstIndex] = getLowSurrogate(codePoint);
            return 2;
        } else {
            dst[dstIndex] = (char) codePoint;
            return 1;
        }
    }

    public static int toCodePoint(char highSurrogate, char lowSurrogate) {
        return MIN_SUPPLEMENTARY_CODE_POINT + ((highSurrogate & 1023) << 10) + (lowSurrogate & 1023);
    }

    public static native char toLowerCase(char c) /*-{
    return String.fromCharCode(c).toLowerCase().charCodeAt(0);
    }-*/;

    public static String toString(char x) {
        return String.valueOf(x);
    }

    public static native char toUpperCase(char c) /*-{
    return String.fromCharCode(c).toUpperCase().charCodeAt(0);
    }-*/;

    public static Character valueOf(char c) {
        if (c < 128) {
            Character result = CharChache.values[c];
            if (result == null) {
                result = CharChache.values[c] = new Character(c);
            }
            return result;
        }
        return new Character(c);
    }

    static int codePointAt(CharSequence cs, int index, int limit) {
        char hiSurrogate = cs.charAt(index++);
        char loSurrogate;
        if (Character.isHighSurrogate(hiSurrogate) && index < limit
                && Character.isLowSurrogate(loSurrogate = cs.charAt(index))) {
            return Character.toCodePoint(hiSurrogate, loSurrogate);
        }
        return hiSurrogate;
    }

    static int codePointBefore(CharSequence cs, int index, int start) {
        char loSurrogate = cs.charAt(--index);
        char highSurrogate;
        if (isLowSurrogate(loSurrogate) && index > start
                && isHighSurrogate(highSurrogate = cs.charAt(index - 1))) {
            return toCodePoint(highSurrogate, loSurrogate);
        }
        return loSurrogate;
    }

    static char getHighSurrogate(int codePoint) {
        return (char) (MIN_HIGH_SURROGATE
                + (((codePoint - MIN_SUPPLEMENTARY_CODE_POINT) >> 10) & 1023));
    }

    static char getLowSurrogate(int codePoint) {
        return (char) (MIN_LOW_SURROGATE + ((codePoint - MIN_SUPPLEMENTARY_CODE_POINT) & 1023));
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}
